package com.tornainbow;

import com.google.common.io.Files;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author mcrysler
 */
public class LocalFileSelector extends Processor {
    // Define a logger for this class
    private static Logger logger = LoggerFactory.getLogger(LocalFileSelector.class);
    
    // Define the required properties
    public static final String PROP_SOURCE_DIRECTORY = "source.directory";
    public static final String PROP_KEEP_ORIGINAL = "keep.original";
    
    // Define default values for the properties
    private static final String DEFAULT_SOURCE_DIRECTORY = "";
    private static final String DEFAULT_KEEP_ORIGINAL = "false";
    
    /**
     * Set the properties for this class
     */
    public LocalFileSelector() {
        super();
        properties.putIfAbsent(PROP_SOURCE_DIRECTORY, DEFAULT_SOURCE_DIRECTORY);
        properties.putIfAbsent(PROP_KEEP_ORIGINAL, DEFAULT_KEEP_ORIGINAL);
    }
    
    @Override
    public String getProcessorName() {
        return (this.getClass().getName() + " : " + this.getName());
    }
    
    @Override
    public void run() {
        // Get the source directory
        String sourceDirectory = this.getProperty(PROP_SOURCE_DIRECTORY);
        File sourceDir = new File(sourceDirectory);
        if (!sourceDir.exists() || !sourceDir.canRead() || !sourceDir.isDirectory()) {
            logger.error("The source directory {} either doesn't exist, is unreadable or isn't a directory", sourceDir.getAbsolutePath());
            return;
        }
        logger.debug("Setting sourceDirectory to {}", sourceDirectory);
        
        // Get the keep original flag
        String keepOriginalVal = this.getProperty(PROP_KEEP_ORIGINAL);
        boolean keepOriginal = Boolean.parseBoolean(keepOriginalVal);
        logger.debug("Setting keepOriginal to {}", keepOriginal);
        
        try {
            // Get the default file system
            FileSystem fs = FileSystems.getDefault();
            
            // Create a new WatchService from the default file system
            WatchService ws = fs.newWatchService();
            
            // Define the Path object that points to the input directory
            Path inputDirectory = fs.getPath(sourceDirectory);
            logger.info("Watching directory: {}", inputDirectory);
            
            // Register this Path with the WatchService and indicate we only
            // care about when files have been created in the directory
            WatchKey key = inputDirectory.register(ws, StandardWatchEventKinds.ENTRY_CREATE);
            
            // Listen for new files to appear in the directory
            for (;;) {
                // Wait for the key to be signaled
                WatchKey wk = null;
                try {
                    wk = ws.take();
                } catch (InterruptedException e) {
                    logger.error("Failed to signal the key", e);
                    return;
                }

                // Since we have received an event, loop over them and process
                // them accordingly
                for (WatchEvent<?> event : wk.pollEvents()) {
                    // Obtain the type of event
                    WatchEvent.Kind<?> kind = event.kind();

                    // This key is registered for ENTRY_CREATE events,
                    // all others should be discarded
                    if (kind == StandardWatchEventKinds.OVERFLOW || 
                            kind == StandardWatchEventKinds.ENTRY_DELETE) {
                        continue;
                    }

                    // The filename is the context of the event.
                    WatchEvent<Path> ev = (WatchEvent<Path>) event;
                    Path fullPath = inputDirectory.resolve(ev.context());
                    logger.info("Picked up file: {}", fullPath);

                    // Get the bytes from the Path object
                    File file = fullPath.toFile();
                    logger.debug("Converted Path object to a File object");

                    byte[] payload = new byte[0];
                    try {
                        payload = Files.toByteArray(file);
                    } catch (FileNotFoundException e) {
                        logger.error("Failed to transfer the file to the queue", e);
                        continue;
                    }
                    logger.debug("Converted contents of File object to byte array");

                    // Create a new Record object
                    Record record = new Record();
                    logger.debug("Instantiated new Record object");

                    // Set the payload
                    record.setPayload(payload);
                    logger.debug("Set payload of Record object");

                    // Send the message to the queue
                    Producer.sendMessage(this.getProperty(PROP_JMS_URL), this.getProperty(PROP_JMS_DESTINATION_QUEUE_NAME), record);
                    logger.info("Sent message to queue {}", this.getProperty(PROP_JMS_DESTINATION_QUEUE_NAME));
                    
                    // Reset the key -- this step is critical if you want to
                    // receive further watch events.  If the key is no longer valid,
                    // the directory is inaccessible so exit the loop.
                    boolean valid = key.reset();
                    if (!valid) {
                        break;
                    }
                    
                    // If the keepOriginal flag is set to false, delete the file
                    // otherwise change the lastModified date so the processor
                    // will trigger another WatchEvent
                    if (!keepOriginal) {
                        file.delete();
                        logger.debug("Deleted file from file system");
                    } else {
                        file.setLastModified(System.currentTimeMillis());
                        logger.info("here");
                    }
                }

                // Reset the key -- this step is critical if you want to
                // receive further watch events.  If the key is no longer valid,
                // the directory is inaccessible so exit the loop.
                boolean valid = key.reset();
                if (!valid) {
                    break;
                }
            }
        } catch (IOException e) {
            logger.error("Failed to create and/or register the WatchService object for the designated file system", e);
        }
    }
}
